TypeScript 55.4%
Python 43.2%
SQL 1.2%
1import Link from "next/link";2import type { Metadata } from "next";3import { notFound } from "next/navigation";4import { EventRow } from "@/components/event-row";5import { LiveFeed } from "@/components/live-feed";6import { Badge, Bar, Chip, Empty, PageHeader, Panel, Stat, Table, Td, TierBadge } from "@/components/ui";7import { api } from "@/lib/api";8import { feedHref, fmtInt, relTime, typeLabel } from "@/lib/format";910export const dynamic = "force-dynamic";1112/** Section order and labels for the desk (spec §104). Unknown categories are appended in API order. */13const SECTIONS: { key: string; label: string }[] = [14 { key: "government", label: "Government" },15 { key: "finance", label: "Business & markets" },16 { key: "infrastructure", label: "Infrastructure" },17 { key: "health", label: "Health" },18 { key: "news", label: "Media" },19 { key: "cyber", label: "Cyber" },20 { key: "ai", label: "AI" },21 { key: "science", label: "Science" },22];2324export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }): Promise<Metadata> {25 const { slug } = await params;26 const d = await api.country(slug);27 if (!d) return { title: "Country not found" };28 return { title: `${d.country.name} desk`, description: `Government, business, infrastructure, health, media, cyber, AI and science signals from ${d.country.name}, detected live by WebSensor.`, alternates: { canonical: `/country/${slug}` } };29}3031export default async function CountryPage({ params }: { params: Promise<{ slug: string }> }) {32 const { slug } = await params;33 const d = await api.country(slug);34 if (!d) notFound();35 const code = d.country.code;36 const name = d.country.name;37 const events24 = d.sources.reduce((n, s) => n + (s.events_24h ?? 0), 0);38 const ordered = [...SECTIONS.map((s) => ({ ...s, items: d.by_category.find((c) => c.category === s.key)?.items ?? [] })), ...d.by_category.filter((c) => !SECTIONS.some((s) => s.key === c.category)).map((c) => ({ key: c.category, label: c.category, items: c.items }))];39 const maxType = Math.max(1, ...d.by_type.map((t) => t.n));40 const firstParty = d.sources.filter((s) => s.first_party !== false).length;41 return (42 <>43 <PageHeader44 compact45 kicker={<span className="flex items-center gap-2"><Link href="/country" className="hover:text-fg">Countries</Link> <span className="text-fg-subtle">/</span> <span className="font-mono">{code}</span></span>}46 title={<span className="flex items-center gap-2"><span aria-hidden>{d.country.flag}</span> {name}</span>}47 description={`Official government, business, infrastructure, health and media signals from ${name}, grouped by desk. Sources are the organizations' own channels; media reports are marked EXTERNAL.`}48 actions={<Link href={feedHref({ country: code }, "/live")} className="rounded-md border border-line bg-panel px-2.5 py-1 text-[12px] hover:border-line-strong">Live feed →</Link>}49 />50 <div className="panel mb-4 grid grid-cols-2 divide-x divide-y divide-line sm:grid-cols-4 sm:divide-y-0">51 <Stat label="Sources" value={fmtInt(d.sources.length)} hint={`${fmtInt(firstParty)} first-party · ${fmtInt(d.sources.length - firstParty)} media`} />52 <Stat label="Events · 24 h" value={fmtInt(events24)} tone="signal" hint="meaningful signals" />53 <Stat label="Breaking · 24 h" value={fmtInt(d.breaking.length)} tone={d.breaking.length ? "hot" : undefined} hint="signal ≥ 80" />54 <Stat label="Silent" value={fmtInt(d.silent.length)} tone={d.silent.length ? "silent" : undefined} hint="no matching announcement" />55 </div>5657 <div className="grid gap-4 xl:grid-cols-[1fr_360px]">58 <div className="flex min-w-0 flex-col gap-4">59 <Panel title={<span className="text-hot">Major events · 24 h</span>} dense action={<Link href={feedHref({ country: code, signal_min: 80 }, "/live")} className="text-[11px] text-fg-subtle hover:text-fg">live →</Link>}>60 {d.breaking.length ? d.breaking.map((e) => <EventRow key={e.id} ev={e} />) : <Empty>No breaking signal from {name} in the last 24 h.</Empty>}61 </Panel>62 <div className="grid gap-4 md:grid-cols-2">63 {ordered.map((s) => (64 <Panel key={s.key} title={s.label} dense action={<Link href={feedHref({ country: code, category: s.key }, "/live")} className="text-[11px] text-fg-subtle hover:text-fg">live →</Link>}>65 {s.items.length ? s.items.map((e) => <EventRow key={e.id} ev={e} />) : <Empty>No {s.label.toLowerCase()} signal from {name} recently.</Empty>}66 </Panel>67 ))}68 </div>69 <LiveFeed initial={d.recent} initialCursor={d.nextCursor} extraQuery={{ country: code }} initialFilters={{ country: code }} showTabs={false} title={`LIVE · ${name.toUpperCase()}`} />70 </div>71 <aside className="flex min-w-0 flex-col gap-4">72 <Panel title={<span className="text-silent">Silent changes</span>} dense action={<Link href={feedHref({ country: code, silent_change: true }, "/live")} className="text-[11px] text-fg-subtle hover:text-fg">all →</Link>}>73 {d.silent.length ? d.silent.map((e) => <EventRow key={e.id} ev={e} />) : <Empty>No silent change from {name}.</Empty>}74 </Panel>75 <Panel title="By type · 7 d" dense>76 {d.by_type.length ? (77 <ul className="divide-y divide-line">78 {d.by_type.map((t) => (79 <li key={t.event_type} className="grid grid-cols-[9rem_1fr_3.5rem] items-center gap-2 px-3 py-1.5 text-[12.5px]">80 <Link href={feedHref({ country: code, event_type: t.event_type }, "/live")} className="truncate hover:underline">{typeLabel(t.event_type)}</Link>81 <Bar value={t.n} max={maxType} tone="info" />82 <span className="text-right font-mono text-fg-subtle tabular">{fmtInt(t.n)}</span>83 </li>84 ))}85 </ul>86 ) : (87 <Empty />88 )}89 </Panel>90 </aside>91 </div>9293 <Panel title={`Sources · ${d.sources.length}`} dense className="mt-4" action={<Link href={`/sources?country=${code}`} className="text-[11px] text-fg-subtle hover:text-fg">filter sources →</Link>}>94 {d.sources.length === 0 ? (95 <Empty>No source is attributed to {name} yet.</Empty>96 ) : (97 <Table head={["Tier", "Source", "Domain", "Kind", "Categories", "Sensors", "Events 24 h", "Last event"]}>98 {d.sources.map((s) => (99 <tr key={s.id} className="hover:bg-panel-2/60">100 <Td><TierBadge tier={s.tier} /></Td>101 <Td><Link href={`/source/${s.id}`} className="font-medium hover:underline">{s.name}</Link></Td>102 <Td mono><Link href={`/domain/${s.domain}`} className="text-fg-muted hover:underline">{s.domain}</Link></Td>103 <Td>{s.first_party === false ? <Badge kind="external" compact /> : <Badge kind="first-party" compact />}</Td>104 <Td><div className="flex flex-wrap gap-1">{s.categories.slice(0, 3).map((c) => <Chip key={c} href={`/category/${c}`}>{c}</Chip>)}</div></Td>105 <Td mono>{s.sensor_count ?? 0}</Td>106 <Td mono>{fmtInt(s.events_24h ?? 0)}</Td>107 <Td mono className="text-fg-subtle">{s.last_event_at ? relTime(s.last_event_at) : "—"}</Td>108 </tr>109 ))}110 </Table>111 )}112 </Panel>113 </>114 );115}116